/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 2004, 2011 Oracle and/or its affiliates.  All rights reserved.
 *
 * $Id$ 
 */

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <iostream>
#include <cassert>
#include "MyDb.hpp"

extern std::vector<IndexDbS *> __idbs_all;
extern std::vector<IndexDbI *> __idbi_all;

IdManager * IdManager::_instance = NULL;
//DbEnv *__dbenv = NULL;


void initDbEnv(){
/*  if(__dbenv) return;
  __dbenv = new DbEnv(0);
//  __dbenv->set_flags(DB_TXN_NOSYNC,1);
  __dbenv->set_cachesize(2,0,1);
  __dbenv->open("./data/", DB_CREATE|DB_INIT_MPOOL|DB_PRIVATE,0);*/
}
/*
DbEnv * getDbEnv(){
  if(!__dbenv) initDbEnv();
  return __dbenv;
}
*/
struct ColType
{
  DataType    type;
  OFFSET size;
  ColType (const string & ts)
  {
    if (ts.compare (0, 3, "INT") == 0)
      {
	type = INTEGER;
	size = 4;
      }
    else if (ts.compare (0, 7, "VARCHAR") == 0)
      {
	type = VARCHAR;
	size = std::atoi (ts.substr (8, ts.length () - 9).c_str ());
      }
    else
      {
	cerr << "ERROR: unknown type '" << ts << "'" << endl;
      }
  }
  void
  dump (ostream & os)
  {
    os << type << '\t' << size  << endl;
  }
};


// File: MyDb.cpp

// Class constructor. Requires a path to the location
// where the database is located, and a database name
MyDb::MyDb ( const std::string & table_name, const std::vector < std::string > &column_name0, 
	const std::vector < std::string > &type0, const vector<string> &pkey):
   varlen_cnt (0), constlen_cnt (0), record_max_size (0),
									// Instantiate Db object
dbFileName_ ("./data/"+table_name + ".db"),	// Database file name
cFlags_ (TreeDB::OWRITER|TreeDB::OCREATE|TreeDB::OTRUNCATE),cur_col_id(1)
  // allow it to be created.
{
  vector<string> column_name(column_name0);
  vector<string> type(type0);

  string pkey_colname = table_name+"_PKEY__";
  column_name.push_back(pkey_colname);
  type.push_back(string("VARCHAR(256)"));

  id2idx = new int[MAX_NAME_HASH];
  memset(id2idx,0, sizeof(int)*MAX_NAME_HASH);

  tune_options(TreeDB::TSMALL|TreeDB::TLINEAR);
 // tune_map(512LL << 20);

    // Open the database
    bool oret = open (dbFileName_.c_str (), cFlags_);
	if(!oret){
      std::cerr << "Error opening database: " << dbFileName_ << "\n";
      std::cerr << error().name() << std::endl;
	}

  IdManager *im = IdManager::getInstance();
  tableId = im->addTable(table_name);
  vector<ColType> ct;
  for(int i=0;i<column_name.size();i++){
	im->addColumn(column_name[i],tableId);
	ColType tmp(type[i]);
	record_max_size += tmp.size ;
	ct.push_back(tmp);
  }

  data_buffer = (_byte*)malloc(4*
	  (record_max_size+sizeof(OBJID)*varlen_cnt+4));

  data_buffer_r = (_byte*)malloc(4*
	  (record_max_size+sizeof(OBJID)*varlen_cnt+4));

  vector<OBJID> origCol = im->getAllId(column_name);
  assert(origCol.size()==column_name.size());
  reorder.resize(column_name.size(),0);
//int
  for(int i=0;i<column_name.size();i++){
    if(ct[i].type==INTEGER){
	  reorder[columns.size()] = i;
          id2idx[origCol[i]] = columns.size();
	  columns.push_back(origCol[i]);
	  constlen_cnt++;
	}
  }
  for(int i=0;i<column_name.size();i++){
	if(ct[i].type==VARCHAR){
	  reorder[columns.size()] = i;
          id2idx[origCol[i]] = columns.size();
	  columns.push_back(origCol[i]);
	  varlen_cnt++;
	}
  }
  //assert(columns.size()==origCol.size());
  assert(varlen_cnt+constlen_cnt==origCol.size());

  idx2idxDb.resize(columns.size(),NULL);

  //rows.push_back(NULL); //bottom
  //for(int i=0;i<origCol.size();i++) id2
#if 0
  cerr<<"dump scheme"<<endl;
  for(int i=0;i<reorder.size();i++){
    cerr<<table_name<<' '<<reorder[i]<<' '<<endl;
  }
#endif
  for(int i=0;i<columns.size();i++)
	pkeyIdx[columns[i]] = -1;
  for(int i=0;i<pkey.size();i++){
	OBJID cid = im->getIdByName(pkey[i]);
	pkeyIdx[cid] = i;
  }
  for(int i=0;i<pkey.size();i++){
	for(int j=0;j<column_name0.size();j++){
	  if(pkey[i]==column_name0[j] ){
		pkey2origrow.push_back(j);
		break;
	  }
	}
  }
  assert(pkey2origrow.size()==pkey.size());
  pkcolid = im->getIdByName(pkey_colname);
  createIndexDbByOBJID(pkcolid);

}

// Private member used to close a database. Called from the class
// destructor.
void
MyDb::closedb ()
{
  // Close the db
/*    if(close ())
    std::cout << "Database " << dbFileName_ << " is closed." << std::endl;
	else
	  std::cerr<<"Database "<<dbFileName_<<" ERROR:"<<error().name()<<endl;
	  */
}

string MyDb::extractPKey(const std::vector<std::string> &row0){
  string ret("'");
  for(int i=0;i<pkey2origrow.size();i++){
	const string &tmp = row0[pkey2origrow[i]];
	if(tmp[0]=='\''){
	  ret.append(tmp.substr(1,tmp.length()-2));
	}else{
	  ret.append(tmp);
	}
	ret.append(",");
  }
  ret.append("'");
  //cerr<<ret<<endl;
  return ret;
}

OBJID MyDb::insertRow(const std::vector<std::string> &row0){
  //cerr<<dbFileName_<<' '<<columns.size()<<' '<<row.size()<<endl;
  std::vector<string> row(row0);
  row.push_back(extractPKey(row0));

  assert(columns.size()==row.size());
  int n = row.size();
  OFFSET offset = 0;
  OBJID myid = cur_col_id++;
//  _byte *buf = (_byte*)malloc(record_max_size); 
  *(OBJID*)(data_buffer+offset) = myid;
  //offset += 4;
  for(int i=0;i<constlen_cnt;i++){
	int tmp = std::atoi(row[reorder[i]].c_str());
	*(int*)(data_buffer + getConstOffset()+sizeof(int)*i) 
	  = tmp;
	if(getIndexDbByIndex(i)){
	  IndexDb<int> *idb = (IndexDb<int>*)getIndexDbByIndex(i);
	  idb->insert(pair<int,OBJID>(tmp,myid));
	}
  }
  offset = getVarOffset();
  for(int i=constlen_cnt;i<n;i++){
	int len = row[reorder[i]].length()-2;
    *(OFFSET*)(data_buffer+sizeof(OFFSET)*(i-constlen_cnt)+sizeof(OBJID))
	  = offset+len;
	memcpy(data_buffer+offset,row[reorder[i]].c_str()+1,len);
	offset += len;
	if(getIndexDbByIndex(i)){
	  IndexDb<string> *idb = (IndexDb<string>*)getIndexDbByIndex(i);
	  const string& ts = row[reorder[i]];
	  idb->insert(pair<string,OBJID>(ts.substr(1,ts.length()-2) ,myid));
	}
  }
  OBJID tmpid = BL_CONV(myid);
/*  Dbt key(&tmpid, sizeof(OBJID));
  Dbt data(data_buffer, offset);

  int ret = put(NULL, &key, &data, DB_NOOVERWRITE);
  if(ret == DB_KEYEXIST)
	err(ret, "ERROR: ");
*/
  bool ret = set((char*)&tmpid, sizeof(OBJID), (char*)data_buffer, offset);
  assert(ret);
  //_byte *tmpbuf =(_byte*) malloc(sizeof(_byte)*offset);
  //memcpy(tmpbuf, data_buffer, sizeof(_byte)*offset);
  //rows.push_back(tmpbuf);

  return myid;
}

bool MyDb::fetchRow(OBJID rid, const _byte **buf){
  OBJID tmpid = BL_CONV(rid);
//  Dbc *cursorp;
//  Dbt key(&tmpid, sizeof(OBJID)), data;
//  cursor (NULL, &cursorp, 0);
//  ret = cursorp->get (&key, &data, DB_SET);
  size_t data_size;
  _byte * data = (_byte*) get((char*)&tmpid, sizeof(OBJID), &data_size);
  //assert(rid>0 && rid<rows.size());
  //_byte *data = rows[rid];
  if(data){
    //*buf = (_byte*)malloc(data_size);
    memcpy(data_buffer_r, data, data_size);
	delete [] data;
	*buf = data_buffer_r;
  //  cursorp->close ();
    return true;
  }else{
  //  cursorp->close ();
    return false;
  }
}



void MyDb::dumpRows(){
  cerr<<"Dump "<<dbFileName_<<endl;
  //Dbc *cursorp;
  //cursor (NULL, &cursorp, 0);
  //assert (cursorp != NULL);
  int ret;
  //Dbt key, data;
  //os << "Dumping Table: " << info.tableName << endl;
  //DB::Cursor *cursorp = cursor();
  //cursorp->jump();
  //OBJID *key;
  //const _byte *data=0; 
  //size_:t key_size, data_size;
#if 0  
  int cnt = 1;
  while (cnt<rows.size() )
    {
      //_byte *dd = (_byte *) data.get_data ();
	  const _byte *dd = rows[cnt];
     // dumpKeyBuffer (os, (_byte *) key.get_data (), info);
     // dumpRowBuffer (os, dd, info);
     cerr<<"KEY="<<(OBJID)cnt<<'#';
       cerr<<"ID+"<<getRowId(dd)<<'#';
     /*for(int i=0;i<constlen_cnt;i++) cerr<<getIntByIndex(i,dd)<<'#';
     for(int i=constlen_cnt;i<columns.size();i++) {
        string tmp = getStringByIndex(i,dd);
        cerr<<tmp.length()<<' '<<tmp<<'#';
     }*/
     for(int i=0;i<columns.size();i++) cerr<<getCSVItemByIndex(i,dd)<<',';
     cerr<<endl;
	 cnt++;
	// delete [] ((char*)key);
    }
//  cursorp->close ();
  for(int i=0;i<columns.size();i++){
    if(idx2idxDb[i]==NULL) continue;
	cerr<<"DUMP: IndexDb "<<i<<endl;
	if(i<constlen_cnt)
	  ((IndexDb<int>*)idx2idxDb[i])->dumpData();
	else
	  ((IndexDb<string>*)idx2idxDb[i])->dumpData();
  }
#endif
}


bool MyDb::checkRow(const vector<CondInfo> &cond, const _byte *buf){
  for(int i=0;i<cond.size();i++){
/*	assert(cond[i].type!=CT_UNKNOWN);
	if(cond[i].type==CT_JOIN|| cond[i].op1_table!=tableId) {
	  cerr<<"NOT my condition!"<<endl;
	  exit(1);
	}*/
	int idx = id2idx[cond[i].op1];
	switch(cond[i].type){
	  case CT_CONST_STRING:
		if(getStringByIndex(idx, buf)!=cond[i].str_op2)
		  return false;
		break;
	  case CT_CONST_INT:
		if(getIntByIndex(idx,buf)!=cond[i].int_op2)
		  return false;
		break;
	  case CT_LESS:
              if(getIntByIndex(idx,buf)>=cond[i].int_op2) {
		  return false;
              }
		break;
	  case CT_GREATER:
              if(getIntByIndex(idx,buf)<=cond[i].int_op2) {
		  return false;
              }
			  break;
	  case CT_RANGE:{
              int x = getIntByIndex(idx,buf);
			  //cerr<<x<<' '<<cond[i].range.second<<' '<<cond[i].range.first<<endl;
			  if(x>cond[i].range.second || x<cond[i].range.first)
				return false;
					}
					break;
	  default: assert(0);
	}
  }
  return true;
}
/*
#define __CHECK_ROW(buf){  \
  __flag = 1; \
  fetchRow(iter->second[i],&buf);\
  for(__i=0;__i<cond.size() && __flag;__i++){ \
	idx = id2idx[cond[__i].op1]; \
	switch(cond[__i].type){  \
	  case CT_CONST_STRING: \
		if(getStringByIndex(idx, buf)!=cond[__i].str_op2){__flag=0;} \
		break; \
	  case CT_CONST_INT: \
		if(getIntByIndex(idx,buf)!=cond[__i].int_op2){__flag=0;} \
		break; \
	  case CT_LESS: \
              if(getIntByIndex(idx,buf)>=cond[__i].int_op2) { \
		       __flag = 0;\
			  }\
		break;\
	  case CT_GREATER:\
              if(getIntByIndex(idx,buf)<=cond[__i].int_op2) {\
		        __flag = 0;\
              }}}\
  if(__flag){\
     ht.push_back(iter->second[i]);\
  }}

*/
/*
bool adv_less_cond(const CondInfo &ci1, const CondInfo &ci2){
  bool b1 = (ci1.type==CT_LESS)||(ci1.type==CT_GREATER);
  bool b2 = (ci2.type==CT_LESS)||(ci2.type==CT_GREATER);
  return (!b1)&&b2;
}
*/
int MyDb::filterRowCond(const vector<CondInfo> &cond0, RowHash &ht){
    int __flag, __i, idx;
	const _byte * __buf;
	ht.clear();
    if(cond0.empty()) return -1;
    vector<CondInfo> cond(cond0);
//    for(int i=0;i<cond.size();i++) {
//	  cond[i].op1 = cid2index(cond[i].op1); 
	  //cond[i].op2_table = (OBJID)(this);
//	}
    //sort(cond.begin(),cond.end(),less_cond);
	CondInfo ci = cond[0];
	ci.op1 = cid2index(ci.op1);
	cond.erase(cond.begin());
	int cnt = 0;
	switch(ci.type){
        case CT_CONST_STRING:{
            IndexDbS *idb = (IndexDbS*)getIndexDbByIndex(ci.op1);
	       // assert(idb);
			IndexDbS::Iter iter = idb->data.find(ci.str_op2);
		    if(iter!=idb->data.end()){
			  for(int i=0;i<iter->second.size();i++){
				_checkAndModifyRHash(ht,cond,iter->second[i]);
				//__CHECK_ROW(__buf);
			  }
			}
        }
            break;
        case CT_CONST_INT:{
	         IndexDbI *idb = (IndexDbI*)getIndexDbByIndex(ci.op1);
	       // assert(idb);
			IndexDbI::Iter iter = idb->data.find(ci.int_op2);
		    if(iter!=idb->data.end()){
			  for(int i=0;i<iter->second.size();i++){
				_checkAndModifyRHash(ht,cond,iter->second[i]);
				//__CHECK_ROW(__buf);
			  }
			}

        }
            break;
		
        case CT_GREATER:{
	    IndexDbI *idb = (IndexDbI*)getIndexDbByIndex(ci.op1);
	   // assert(idb);
            for(IndexDbI::RIter iter=idb->data.rbegin();iter!=idb->data.rend();++iter){
                if(iter->first<=ci.int_op2 ) break;
				for(int i=0;i<iter->second.size();i++){
			      _checkAndModifyRHash(ht,cond,iter->second[i]);
				//  __CHECK_ROW(__buf);
				}
            }
        }
            break;
        case CT_LESS:{
	    IndexDbI *idb = (IndexDbI*)getIndexDbByIndex(ci.op1);
	   // assert(idb);
            for(IndexDbI::Iter iter=idb->data.begin();iter!=idb->data.end();++iter){
                if(iter->first>=ci.int_op2 ) break;
				for(int i=0;i<iter->second.size();i++){
			      _checkAndModifyRHash(ht,cond,iter->second[i]);
				//__CHECK_ROW(__buf);
				}
            }
        }
            break;
	    case CT_RANGE:{
				//cerr<<"DFD"<<endl;	assert(0);
   IndexDbI *idb = (IndexDbI*)getIndexDbByIndex(ci.op1);
	   // assert(idb);
            for(IndexDbI::Iter iter=idb->data.lower_bound(ci.range.first);iter!=idb->data.end();++iter){
                if(iter->first>ci.range.second ) break;
				for(int i=0;i<iter->second.size();i++){
			      _checkAndModifyRHash(ht,cond,iter->second[i]);
				//__CHECK_ROW(__buf);
				}
            }

					  }
					  break;
	    default:assert(0);

	}

	return cnt;

}

void MyDb::createIndexDbByOBJID(OBJID cid){
      int idx = id2idx[cid];
	  IndexDbI *idbi = 0;
	  IndexDbS *idbs = 0;
      if(getIndexDbByIndex(idx)) return; //already has a indexdb on cid
      // cerr<<"@@ Create IndexDb on "<< dbFileName_<<" col: "<<IdManager::getInstance()->getNameById(cid)<<" idx:"<<idx<<endl;
      //IndexDb *idb = new IndexDb();
	  switch(getTypeByIndex(idx)){
		case INTEGER:
		  idbi = (new IndexDb<int>(cid));
		  assert(idbi);
          idx2idxDb[idx] = (void*)idbi;
		  __idbi_all.push_back(idbi);
		  break;
		case VARCHAR:
		  idbs = (new IndexDb<string>(cid));
		  assert(idbs);
          idx2idxDb[idx] = (void*)idbs;
		  __idbs_all.push_back(idbs);
		  break;
		default:
		  assert(0);
	  }
      //colid2idxDb[cid] = idb;
   }

